﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace game
{
    public class Billboard
    {
        public class Factory : FactoryPool<Billboard>
        {
            public Factory(Scene scene, int initialSize) : base(scene, initialSize) { }

            public override Billboard CreateObject()
            {
                Billboard ep = new Billboard(scene);
                return ep;
            }
        }

        public Vector3 Position { get { return position; } set { position = value; } }
        public float Rotation { get { return rotation; } set { rotation = value; rotationChanged = true; } }
        public Vector3 Velocity { get { return velocity; } set { velocity = value; } }
        
        public bool IsAlive { get; protected set; }
        public Texture Texture { get; protected set; }
        public float MaxLifeTimeMs { get; set; }

        public BlendingMode Blending;

        public bool Fading { get; set; }

        public enum BlendingMode
        {
            ADDITIVE,
            REPLACE,
        }


        protected Scene scene;

        protected Vector3 position;
        protected float rotation;
        protected bool rotationChanged;
        protected Vector3 velocity;

        protected float lifeTimeMs;

        protected Vector3 axis;
        protected bool axisAligned = false;

        protected BillboardData[] verts = new BillboardData[4];

        // statics

        protected static Effect effect;
        protected static EffectTechnique additiveBlendingEffectTech;
        protected static EffectTechnique replaceBlendingEffectTech;
        protected static EffectTechnique axisAlignedEffectTech;
        protected static EffectTechnique fadingEffectTech;
        protected static EffectParameter ep_gWVP;
        protected static EffectParameter ep_gEyePosW;
        protected static EffectParameter ep_gTex;
        protected static EffectParameter ep_gBillboardAxis;

        protected static Texture dummyTexture;

        protected static short[] indices = new short[6] { 0, 1, 2, 0, 2, 3 };

        static Billboard()
        {

            effect = ShooterGame.Instance.Content.Load<Effect>("Effects\\Billboard");
            additiveBlendingEffectTech = effect.Techniques["BillboardAdditiveBlending"];
            replaceBlendingEffectTech = effect.Techniques["BillboardReplaceBlending"];
            axisAlignedEffectTech = effect.Techniques["AxisAlignedBillboard"];
            fadingEffectTech = effect.Techniques["FadingBillboard"];

            ep_gWVP = effect.Parameters["gWVP"];
            ep_gEyePosW = effect.Parameters["gEyePosW"];
            ep_gTex = effect.Parameters["gTex"];
            ep_gBillboardAxis = effect.Parameters["gBillboardAxis"];

            dummyTexture = ShooterGame.Instance.Content.Load<Texture>("Textures\\dummytex");
        }

        public Billboard(Scene scene)
        {
            this.scene = scene;
            Texture = dummyTexture;


            verts[0].Position = Vector3.Zero;
            verts[0].TexCoord = new Vector2(1, 1);
            verts[1].Position = Vector3.Zero;
            verts[1].TexCoord = new Vector2(0, 1);
            verts[2].Position = Vector3.Zero;
            verts[2].TexCoord = new Vector2(0, 0);
            verts[3].Position = Vector3.Zero;
            verts[3].TexCoord = new Vector2(1, 0);

            Reset();
        }

        public void SetTexture(Texture texture)
        {
            Texture = texture;
        }

        public void SetTexture(String filename)
        {
            Texture = ShooterGame.Instance.Content.Load<Texture>(filename);
        }

        public void SetTexturePart(Texture2D texture, int wStartPx, int hStartPx, int wEndPx, int hEndPx)
        {
            int w = texture.Width;
            int h = texture.Height;

            if (w == 0 || h == 0)
            {
                throw new Exception("AnimTexture: width or height of texture equals zero.");
            }

            if (wEndPx != 0 && hEndPx != 0)
            {
                verts[0].TexCoord = new Vector2((float)wStartPx / (float)w, (float)hStartPx / (float)h);
                verts[1].TexCoord = new Vector2((float)wStartPx / (float)w, (float)hEndPx / (float)h);
                verts[2].TexCoord = new Vector2((float)wEndPx / (float)w, (float)hEndPx / (float)h);
                verts[3].TexCoord = new Vector2((float)wEndPx / (float)w, (float)hStartPx / (float)h);
            }
        }

        public void SetTexturePart(String filename, int wStartPx, int hStartPx, int wEndPx, int hEndPx)
        {
            Texture2D tex = ShooterGame.Instance.Content.Load<Texture2D>(filename);
            SetTexturePart(tex, wStartPx, hStartPx, wEndPx, hEndPx);
        }

        public void SetSize(int width, int height)
        {
            verts[0].Offset = new Vector2(-width, -height);
            verts[1].Offset = new Vector2(width, -height);
            verts[2].Offset = new Vector2(width, height);
            verts[3].Offset = new Vector2(-width, height);
        }

        public void SetAxis(Vector3 axis)
        {
            this.axis = axis;
            this.axisAligned = true;

            verts[0].Axis = axis;
            verts[1].Axis = axis;
            verts[2].Axis = axis;
            verts[3].Axis = axis;
        }

        public void UnsetAxis()
        {
            this.axisAligned = false;
        }

        public void Reset()
        {
            axisAligned = false;
            MaxLifeTimeMs = 0.0f;
            lifeTimeMs = 0.0f;
            rotation = 0f;
            IsAlive = true;
            Blending = BlendingMode.ADDITIVE;
            Fading = false;

            verts[0].Position = Vector3.Zero;
            verts[1].Position = Vector3.Zero;
            verts[2].Position = Vector3.Zero;
            verts[3].Position = Vector3.Zero;

            verts[0].Color = Color.White;
            verts[1].Color = Color.White;
            verts[2].Color = Color.White;
            verts[3].Color = Color.White;

            verts[0].Energy = 1.0f;
            verts[1].Energy = 1.0f;
            verts[2].Energy = 1.0f;
            verts[3].Energy = 1.0f;

            velocity.X = 0f;
            velocity.Y = 0f;
            velocity.Z = 0f;
        }

        protected void RotateVerts()
        {
            if (rotation != 0)
            {
                Vector3 zero = Vector3.Zero;
                Matrix rot = Matrix.CreateRotationY(rotation);
                Vector3.Transform(ref zero, ref rot, out verts[0].Position);
                Vector3.Transform(ref zero, ref rot, out verts[1].Position);
                Vector3.Transform(ref zero, ref rot, out verts[2].Position);
                Vector3.Transform(ref zero, ref rot, out verts[3].Position);
            }
        }

        public void Update(GameTime gameTime)
        {
            if (!IsAlive)
                return;

            lifeTimeMs += gameTime.ElapsedGameTime.Milliseconds;
            if (lifeTimeMs > MaxLifeTimeMs && MaxLifeTimeMs != 0)
            {
                IsAlive = false;
                return;
            }

            Vector3 movement;
            Vector3.Multiply(ref velocity, gameTime.ElapsedGameTime.Milliseconds, out movement);
            Vector3.Add(ref position, ref movement, out position);

            if (Fading && MaxLifeTimeMs != 0.0f)
            {
                float energy = 1.0f - lifeTimeMs / MaxLifeTimeMs; // 1.0f
                float inv = lifeTimeMs / MaxLifeTimeMs;
                if (inv > 0.5f)
                {
                    //energy = 2.0f - inv * 2;
                }
                verts[0].Energy = energy;
                verts[1].Energy = energy;
                verts[2].Energy = energy;
                verts[3].Energy = energy;
            }
        }

        public void Draw(GameTime gameTime)
        {
            if (!IsAlive)
                return;

            

            ShooterGame.Instance.GraphicsDevice.VertexDeclaration = BillboardData.VertexDecl;



            if (rotationChanged)
            {
                RotateVerts();
                rotationChanged = false;

                Vector3.Add(ref verts[0].Position, ref position, out verts[0].Position);
                Vector3.Add(ref verts[1].Position, ref position, out verts[1].Position);
                Vector3.Add(ref verts[2].Position, ref position, out verts[2].Position);
                Vector3.Add(ref verts[3].Position, ref position, out verts[3].Position);
            }
            else
            {
                verts[0].Position = position;
                verts[1].Position = position;
                verts[2].Position = position;
                verts[3].Position = position;
            }


            Matrix wvp;
            Matrix view = scene.ActiveCamera.View;
            Matrix proj = scene.ActiveCamera.Projection;

            wvp = view;
            Matrix.Multiply(ref wvp, ref proj, out wvp);

            ep_gWVP.SetValue(wvp);
            ep_gEyePosW.SetValue(scene.ActiveCamera.Position);
            ep_gTex.SetValue(Texture);
            ep_gBillboardAxis.SetValue(axis);

            if (axisAligned)
                effect.CurrentTechnique = axisAlignedEffectTech;
            else if (Fading)
                effect.CurrentTechnique = fadingEffectTech;
            else if (Blending == BlendingMode.ADDITIVE)
                effect.CurrentTechnique = additiveBlendingEffectTech;
            else
                effect.CurrentTechnique = replaceBlendingEffectTech;

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();

                ShooterGame.Instance.GraphicsDevice.DrawUserIndexedPrimitives
                    <BillboardData>(
                    PrimitiveType.TriangleList, verts, 0, 4, indices, 0, 2);
                pass.End();
            }
            effect.End();
        }
    }
}
